package com.qust.EnDecryption.handle;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.qust.EnDecryption.common.Constant;
import com.qust.EnDecryption.common.MetaData;
import com.qust.EnDecryption.util.SymmetricEncryption;
import com.qust.EnDecryption.zookeeper.NeedChannel;
import com.qust.agreement.Enums.InstructionEnum;
import com.qust.agreement.Message;
import com.qust.agreement.message.ResponseMessage;
import com.qust.agreement.message.TransmissionAlgorithmMessage;
import com.qust.agreement.result.Result;
import com.qust.agreement.result.ResultEnum;
import com.qust.encryptionAndDecryption.IEnDecryption;
import com.qust.encryptionAndDecryption.KeyPair;
import com.qust.encryptionAndDecryption.enDecryption.AbstractEnDecryptionWithKeyPair;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.concurrent.DefaultPromise;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;

import java.io.OutputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Objects;

/**
 * 算法传输handle,用来处理AlgorithmTransmissionContext<br/>
 * AlgorithmTransmissionContext{<br/>
 * <p>&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp code: 算法编码</p>
 * <p>&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp classPath: 算法的类路径</p>
 * <p>&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp jar: jar包的比特数组</p>
 * <p>‼️注意:私钥和公钥是被加密过的,如果要修改加密密钥的密钥,请修改Constant.password,客户端也要同时修改‼️</p>
 * <p>&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp privateKey: 私钥</p>
 * <p>&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp publicKey: 公钥</p>
 * <br/>}<br/>
 * 思路:<br/>
 * 首先保存jar包数据,然后用URLClassLoader加载出算法实例,并且保存到map(算法缓存)中
 */
@Log4j2
@ChannelHandler.Sharable
public class AlgorithmTransmissionHandle extends ChannelInboundHandlerAdapter implements NeedChannel {
    private final String jarPathPrefix = Constant.savePathPrefix; // jar包保存路径
    private Map<Integer, IEnDecryption> map; // 算法缓存
    private Channel channel;
    @Setter
    private InterProcessMutex interProcessMutex;

    public AlgorithmTransmissionHandle(Map<Integer, IEnDecryption> map) {
        this.map = map;
    }

    public AlgorithmTransmissionHandle(Map<Integer, IEnDecryption> map, Channel channel) {
        this(map);
        this.channel = channel;
    }


    /**
     * 激活channel时执行的任务:<br/>
     * 当第一次激活并且mateData.json存在时,解析.json文件,并获取算法实例加载到缓存中
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Path path = Paths.get(jarPathPrefix + "/mateData.json");
        if (Files.exists(path) && !Constant.loadMetaData) {
            log.debug("加载元数据");
            MetaData metaData = JSON.parseObject(path.toFile().toURI().toURL(), MetaData.class);
            Constant.META_DATA.setAlgorithmMetaDataMap(metaData.getAlgorithmMetaDataMap());
            Map<Integer, MetaData.AlgorithmMetaData> algorithmMetaDataMap = metaData.getAlgorithmMetaDataMap();
            log.debug("加载算法实例");
            for (int i : algorithmMetaDataMap.keySet()) {
                MetaData.AlgorithmMetaData algorithmMetaData = algorithmMetaDataMap.get(i);
                try (URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{Paths.get(algorithmMetaData.getJarPath()).toFile().toURI().toURL()})) {
                    log.debug("初始化算法,{}:,{}",i,algorithmMetaDataMap.get(i).getClassPath());
                    Class<?> aClass = urlClassLoader.loadClass(algorithmMetaData.getClassPath());
                    AbstractEnDecryptionWithKeyPair abstractEnDecryptionWithKeyPair = (AbstractEnDecryptionWithKeyPair) aClass.newInstance();
                    log.debug("加载密钥");
                    abstractEnDecryptionWithKeyPair.loadKeyPair(
                            new KeyPair(
                                    SymmetricEncryption.decrypt(algorithmMetaData.getPublicKey(), Constant.password),
                                    SymmetricEncryption.decrypt(algorithmMetaData.getPrivateKey(), Constant.password)
                            )
                    );
                    log.debug("放入缓存");
                    map.put(i, abstractEnDecryptionWithKeyPair);
                }
            }
            Constant.loadMetaData = true;
        } else {
            if (!Constant.loadMetaData) {
                log.debug("该节点没有metaData.json文件");
            }
        }
        super.channelActive(ctx);
    }

    /**
     * 入站时执行的任务
     *
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        Message message = (Message) msg;
        log.debug("进入算法传输,指令{}",message.getInstruction() == InstructionEnum.transmissionAlgorithm.getCode());

        if (message.getInstruction() == InstructionEnum.algorithmicResourceCatchUp.getCode()){
            Constant.isCatchUp = message.getContext().equals("true");
            log.debug("算法传输完成,{}",Constant.isCatchUp);
        }else if (message.getInstruction() == InstructionEnum.transmissionAlgorithm.getCode()) {
            log.debug("进入算法传输handle");
            log.debug("获取AlgorithmTransmissionContext");
            JSONObject temp = (JSONObject) message.getContext();
            Message.AlgorithmTransmissionContext context = temp.to(Message.AlgorithmTransmissionContext.class);
            String classPath = context.getClassPath();
            boolean flag = context.isCatchUp();
            context.setCatchUp(false);
            int code = context.getCode();
            if (channel != null && Constant.isCatchUp) {
                ctx.executor().next().execute(() -> {
                    log.debug("传给下一个节点");
                    DefaultPromise<Message> resultPromise = new DefaultPromise<>(ctx.executor().next());
                    Constant.MESSAGE_HANDLE.setResultPromise(resultPromise);
                    channel.writeAndFlush(new TransmissionAlgorithmMessage(context));
                    resultPromise.addListener(future -> {
                        if (future.isSuccess()) {
                            Message m = (Message) future.get();
                            JSONObject tempContext = (JSONObject) m.getContext();
                            if (Objects.equals(tempContext.to(Result.class).getCode(), ResultEnum.SUCCESS.getCode())) {
                                log.info("算法传输完成");
                            }
                        } else {
                            log.warn("算法传输失败");
                        }
                    });
                });
            }
            if (!flag){ // 如果是追赶调用,则不用保存jar包
                log.debug("准备保存jar包");
                String[] split = classPath.split("\\.");
                String jarPath = jarPathPrefix + "/" + split[split.length - 1] + "_" + code + ".jar";
                byte[] jar = context.getJar();

                try (
                        OutputStream outputStream = Files.newOutputStream(Paths.get(jarPath));
                        OutputStream mateDataOutput = Files.newOutputStream(Paths.get(jarPathPrefix + "/mateData.json"));
                        URLClassLoader classLoader = new URLClassLoader(new URL[]{Paths.get(jarPath).toFile().toURI().toURL()});
                ) {
                    log.debug("保存");
                    outputStream.write(jar);
                    outputStream.flush();
                    getEnDecryption(classLoader, classPath, jarPath, context, code, mateDataOutput);
                    ctx.channel().writeAndFlush(new ResponseMessage(Result.success(JSON.toJSONString(Constant.META_DATA))));
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        } else {
            super.channelRead(ctx, msg);
        }
    }

    /**
     * 获取算法实例,并保存到MateData中
     *
     * @param classLoader
     * @param classPath
     * @param jarPath
     * @param context
     * @param code
     * @param mateDataOutput
     * @throws Exception
     */
    private void getEnDecryption(URLClassLoader classLoader, String classPath, String jarPath, Message.AlgorithmTransmissionContext context, int code, OutputStream mateDataOutput) throws Exception {
        log.debug("读取算法实例");
        log.debug("加载密钥");
        Class<?> aClass = classLoader.loadClass(classPath);
        KeyPair keyPair = new KeyPair(
                SymmetricEncryption.decrypt(context.getPublicKey(), Constant.password),
                SymmetricEncryption.decrypt(context.getPrivateKey(), Constant.password)
        );
        log.debug("实例化算法");
        AbstractEnDecryptionWithKeyPair abstractEnDecryptionWithKeyPair = (AbstractEnDecryptionWithKeyPair) aClass.newInstance();
        abstractEnDecryptionWithKeyPair.loadKeyPair(keyPair);
        log.debug("添加缓存");
        map.put(code, abstractEnDecryptionWithKeyPair);
        log.debug("写入元数据");
        Constant.META_DATA.getAlgorithmMetaDataMap().put(code, new MetaData.AlgorithmMetaData(classPath, jarPath, context.getPublicKey(), context.getPrivateKey()));
        log.debug("持久化元数据");
        mateDataOutput.write(JSON.toJSONString(Constant.META_DATA).getBytes(StandardCharsets.UTF_8));
    }

    @Override
    public void need(Channel channel) {
        this.channel = channel;
    }
}
